package no.sachmuld.methodcombination.impl.annotation;

import no.sachmuld.methodcombination.api.MethodCombinationProtocol;
import org.apache.log4j.Logger;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.Map;

/**
 *
 */
public class BehaviourClassUtils {

    private static Logger logger = Logger.getLogger(BehaviourClassUtils.class);

    public static Class behaviourClassFromMethodCombinationProtocol(Class cls, Class... actualClassArguments) {
        if (cls == null) {
            return null;
        }

        if (cls == MethodCombinationProtocol.class && actualClassArguments.length == 1) {
            return actualClassArguments[0];
        }

        Type[] typeParameters = cls.getTypeParameters();

        Map<Type, Class> formalToActualParameterMap = new HashMap<>();
        if (actualClassArguments.length > 0) {
            for (int i = 0; i < typeParameters.length; i++) {
                formalToActualParameterMap.put(typeParameters[i], actualClassArguments[i]);
            }
        }
        Class superclass = cls.getSuperclass();

        if (superclass != null) {
            Type genericSuperclass = cls.getGenericSuperclass();
            Class result = behaviourClassFromMethodCombinationProtocol(superclass, actualClassArguments(genericSuperclass, formalToActualParameterMap));
            if (result != null) {
                return result;
            }
        }

        Class[] interfaces = cls.getInterfaces();
        Type[] genericInterfaces = cls.getGenericInterfaces();
        for (int i = 0; i < genericInterfaces.length; i++) {
            Type genericInterface = genericInterfaces[i];
            Class result = behaviourClassFromMethodCombinationProtocol(interfaces[i], actualClassArguments(genericInterface, formalToActualParameterMap));
            if (result != null) {
                return result;
            }
        }

        return null;

    }

    private static Class[] actualClassArguments(Type type, Map<Type, Class> formalToActualParameterMap) {

        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            Class[] result = new Class[actualTypeArguments.length];
            for (int i = 0; i < actualTypeArguments.length; i++) {
                if (actualTypeArguments[i] instanceof Class) {
                    result[i] = (Class) actualTypeArguments[i];
                } else if (actualTypeArguments[i] instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType) actualTypeArguments[i];
                    Type rawType = pt.getRawType();
                    if (rawType instanceof Class) {
                        result[i] = (Class) rawType;
                    } else {
                        result[i] = null;
                        logger.error("Raw type not supported: " + rawType.getClass().getName());
                    }
                }
                else if (actualTypeArguments[i] instanceof TypeVariable) {
                  result[i] = formalToActualParameterMap.get(actualTypeArguments[i]);
                } else {
                    result[i] = null;
                    logger.error("Type argument not supported: " + actualTypeArguments[i].getClass().getName());
                }
            }
            return result;
        } else {
            return new Class[0];
        }
    }

}
